package sd;

import java.net.URI;
import java.util.Collection;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import snbenchlib.Log;

/**
 * 
 * This *SINGLETON* object wraps the map of the SXEs that must be maintained by the 
 * Resource Manager.  Allows indexing by URI or by ID with O(1) lookup
 * time in both cases.  Ensures consistency in both hashtables by performing
 * ops on both hashtables.
 *
 * maintains total free resources and the "largest" available SXE.  This is
 * done automatically on put() and remove() operations
 *
 * To maintain concurrency, callers must get the Read/Write lock before 
 * access, as appropriate.
 * 
 * @author Michael
 *
 */
public class SXEMap {
	/* makes sure only one instance occurs */
	static private boolean 						instantiated = false;
	
	static private Hashtable<String,ManagedSXE> htSXEsbyID;
	static private Hashtable<URI,ManagedSXE> 	htSXEsbyURI;
	static private ManagedSXE 					maxAvailSXE;	
	static private int 							totalResources;
	
	private ReentrantReadWriteLock rwLock;
	
	 public SXEMap(){
		if(!instantiated){
			htSXEsbyID = new Hashtable<String,ManagedSXE>();
			htSXEsbyURI = new Hashtable<URI,ManagedSXE>();
			maxAvailSXE = null;
			totalResources = 0;
			rwLock = new ReentrantReadWriteLock();
			
			instantiated = true;
		}	
	}
	
	public void debugResources(){
		Log.rm.log("==========current resource view=============");
		Log.rm.log("TotalResources=" + totalResources);
		if(totalResources>0){
			Log.rm.log("MaxAvailSXE=" + maxAvailSXE.getId());
			Log.rm.log("--------------------------------------------");
			for(ManagedSXE m:htSXEsbyID.values()){
				Log.rm.log(m.toString());
			}
		}
		Log.rm.log("============================================");
	}
	 
	public void getWriteLock(){ rwLock.writeLock().lock(); }
	public void getReadLock(){ rwLock.readLock().lock(); }
	public void releaseWriteLock(){ rwLock.writeLock().unlock(); }
	public void releaseReadLock(){ rwLock.readLock().unlock(); }
 	
	public void put(ManagedSXE s){
		htSXEsbyID.put(s.getId(), s);
		htSXEsbyURI.put(s.getUri(), s);
		totalResources+=s.getAvailResources();
		if(maxAvailSXE==null)
			maxAvailSXE =s;
		else if(s.getAvailResources()>maxAvailSXE.getAvailResources())
			maxAvailSXE =s;
	}
	
	/**
	 * @param u - the URI to match
	 * @return - ManagedSXE w/matching URI or 
	 * @throws NoSuchElementException
	 */
	public ManagedSXE get(URI u) throws NoSuchElementException{
		ManagedSXE s = null;

		s = htSXEsbyURI.get(u);

		if(s==null)
			throw new NoSuchElementException();
		return s;
	}

	/**
	 * @param sID - the ID to match
	 * @return - ManagedSXE w/matching ID or
	 * @throws NoSuchElementException
	 */
	public ManagedSXE get(String sID) throws NoSuchElementException{
		ManagedSXE s = null;

		s = htSXEsbyID.get(sID);

		if(s==null)
			throw new NoSuchElementException();
		return s;
	}
	
	public ManagedSXE remove(String sID)  throws NoSuchElementException{
		ManagedSXE s = null;
		if(!contains(sID))
			throw new NoSuchElementException();
		s = get(sID);
		remove(s);
		return s;
	}

	public ManagedSXE remove(URI u)  throws NoSuchElementException{
		ManagedSXE s = null;
		if(!contains(u))
			throw new NoSuchElementException();
		s = get(u);
		remove(s);
		return s;
	}
	
	private void remove(ManagedSXE s){
		ManagedSXE t = htSXEsbyURI.remove(s.getUri());	
		if(t!=null)
			htSXEsbyID.remove(s.getId());
		totalResources-=t.getAvailResources();
		recomputeMaxAvailSXE();
	}
	
	private void recomputeMaxAvailSXE() {
		ManagedSXE t = null;
		int iMax = 0;

		for(ManagedSXE s: values()){
			if(iMax < s.getAvailResources()){
				iMax = s.getAvailResources();
				t = s;
			}
		}
		maxAvailSXE = t;
	}

	public boolean contains(String sID){

		boolean b=htSXEsbyID.containsKey(sID);

		return b;
	}

	public boolean contains(URI u){
		boolean b=htSXEsbyURI.containsKey(u);
		return b;
	}
		
	public Collection<ManagedSXE> values(){
		return htSXEsbyID.values();
	}
	
	public int getTotalResources(){
		return totalResources;
	}
	
	private int getLargestSXEResources(){
		if(maxAvailSXE==null)
			return 0;
		return maxAvailSXE.getAvailResources();
	}

	private ManagedSXE getMaxAvailSXE(){
		return maxAvailSXE;
	}
	
	/** 
	 * Returns an SXE with enough resources to accomidate the need
	 * given.  
	 * 
	 * @param iResourceNeeds
	 * @return ManagedSXE to be loaded with the program
	 * @throws AdmissionDenied if insufficient resources on any single SXE
	 */
	public ManagedSXE getSXEToAccomidateResourceNeeds(int iResourceNeeds)
		throws AdmissionDenied
	{
		ManagedSXE s = null;
		if(iResourceNeeds>getLargestSXEResources())
			throw new AdmissionDenied("No single SXE has enough free resources to consume " + iResourceNeeds);
		
		s = getMaxAvailSXE();
		ResourcesConsumed(s,iResourceNeeds);
		return s;
	}
	
	/** 
	 * Tries to assign the resources needed to the SXE specified.  Throws admissionDenied
	 * if that can't be done.  If it can be done, resources are consumed.  
	 * 
	 * @param ReqSXE
	 * @param iResourceNeeds
	 * @return ManagedSXE to be loaded with the program
	 * @throws AdmissionDenied if insufficient resources on specified SXE
	 */
	public ManagedSXE getSXEToAccomidateResourceNeeds(URI ReqSXEURI, int iResourceNeeds)
		throws AdmissionDenied, NoSuchElementException
	{
		ManagedSXE d = get(ReqSXEURI);
		if(iResourceNeeds>d.getAvailResources())
			throw new AdmissionDenied("SXE " + d.getId()+ " does not have enough free resources to consume " + iResourceNeeds);
		
		ResourcesConsumed(d,iResourceNeeds);
		return d;
	}
	
	
	
	/**
	 * Decrement resources by given amount.  Recomputes maxAvailSXE
	 * @param m SXE to decrement
	 * @param iAmt amount by which to decrement
	 */
	private void ResourcesConsumed(ManagedSXE m, int iAmt){
		m.decrAvailResources(iAmt);
		totalResources-=iAmt;
		recomputeMaxAvailSXE();
	}

	/**
	 * Increment resources by given amount.  Recomputes maxAvailSXE
	 * @param u the URI of the SXE to which the resources have been returned
	 * @param iAmt the resources returned
	 */
	public void ResourcesReturned(URI u, int iAmt){
		ManagedSXE m = null;
		try{
			m = get(u);
		}
		catch(NoSuchElementException e){
			// RESOURCES COULD NOT BE RETURNED...
			Log.rm.log("Could not return resources to SXE with URI " + u + " because the node is missing!");
		}
		totalResources+=iAmt;
		m.incrAvailResources(iAmt);
		recomputeMaxAvailSXE();
	}

}
